using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using System.Text;

namespace Bread.IoC;

internal record ContainerInfo(string Namespace, string ClassName, bool IsPartial);

[Generator(LanguageNames.CSharp)]
public class IoCContainerGenerator : IIncrementalGenerator
{

    public void Initialize(IncrementalGeneratorInitializationContext context)
    {
        // Find class declarations that inherit from IoCContainer and declare a Bootstrap method.
        var candidateProvider = context.SyntaxProvider
            .CreateSyntaxProvider(predicate: Predicator, transform: Transformer)
            .Where(static o => o != null)!;

        // collect all found containers
        var containers = candidateProvider.Collect();
        var combined = context.CompilationProvider.Combine(containers);

        // generate one file per detected container with a simple marker partial class (safe, non-invasive)
        context.RegisterSourceOutput(combined, static (spc, found) => {
            var compilation = found.Left;
            var foundContainers = found.Right;
            foreach (var f in foundContainers) {
                // skip null entries (defensive)
                if (f == null) continue;
                ProcessRegistration(compilation, f, spc);
            }
        });
    }


    private static bool Predicator(SyntaxNode node, CancellationToken ct)
    {
        if (node is not ClassDeclarationSyntax cls) {
            return false;
        }

        // quick syntactic check: has base type ending with "IoCContainer"
        var hasBase = cls.BaseList?.Types.Any(t => t.ToString().EndsWith("IoCContainer")) ?? false;
        if (!hasBase) {
            return false;
        }

        // quick syntactic check: contains a method named "Bootstrap"
        var bootstrap = cls.Members.OfType<MethodDeclarationSyntax>().FirstOrDefault(o => o.Identifier.Text == "Bootstrap");
        if (bootstrap == null) {
            return false;
        }

        var methods = bootstrap.DescendantNodes().OfType<InvocationExpressionSyntax>().Any();
        return methods;
    }

    private static ContainerInfo? Transformer(GeneratorSyntaxContext ctx, CancellationToken ct)
    {
        var classDecl = (ClassDeclarationSyntax)ctx.Node;
        var symbol = ctx.SemanticModel.GetDeclaredSymbol(classDecl, ct) as INamedTypeSymbol;
        if (symbol == null) return null;

        // Ensure the base type is indeed IoCContainer (semantic check)
        var baseType = symbol.BaseType;
        if (baseType == null) return null;
        if (baseType.Name != "IoCContainer") return null;

        return new ContainerInfo(
            Namespace: symbol.ContainingNamespace?.ToDisplayString() ?? string.Empty,
            ClassName: symbol.Name,
            IsPartial: classDecl.Modifiers.Any(m => m.IsKind(Microsoft.CodeAnalysis.CSharp.SyntaxKind.PartialKeyword))
        );
    }

    private static void ProcessRegistration(Compilation compilation, ContainerInfo info, SourceProductionContext spc)
    {
        // build source hint name
        var hintName = string.IsNullOrEmpty(info.Namespace)
            ? $"{info.ClassName}.ZeroIoC.g.cs"
            : $"{info.Namespace}.{info.ClassName}.ZeroIoC.g.cs";

        var sb = new StringBuilder();

        var parser = new CompilationParser(compilation);
        var invocations = parser.GetRegistMethods(info);
        var entries = parser.GetRegistMethodsSynx(invocations);

        // group by interface and check duplicates
        var grouped = entries.GroupBy(e => e.Interface.ToDisplayString());
        // If duplicates for the same interface, emit a generated file that reports them as comments (do not attempt to generate conflicting registrations)
        var duplicates = grouped.Where(g => g.Count() > 1).ToArray();
        if (duplicates.Length > 0) {
            EmitError(sb, info, duplicates);
            spc.AddSource(hintName, sb.ToString());
            return;
        }

        sb.AppendLine("// This file is generated by Bread.IoC.");
        sb.AppendLine("// <auto-generated/>");
        sb.AppendLine("using System;");
        sb.AppendLine("using System.Linq;");
        sb.AppendLine("using System.Collections.Generic;");
        sb.AppendLine("using Bread.IoC;");

        if (!string.IsNullOrEmpty(info.Namespace)) {
            sb.AppendLine($"namespace {info.Namespace}");
            sb.AppendLine("{");
        }

        sb.AppendLine($"    [System.CodeDom.Compiler.GeneratedCode(\"Bread.IoC\", \"1.0.0\")]");  // 4 spaces indent
        sb.AppendLine($"    public sealed partial class {info.ClassName}");
        sb.AppendLine("    {");

        // internal struct for ICoder
        foreach (var entry in entries) {
            sb.AppendLine($"        private struct {entry.Interface.ToCreatorName()} : ICreator<{entry.Interface.ToGlobalName()}>"); // 8 spaces indent
            sb.AppendLine("        {");

            sb.AppendLine($"            public {entry.Interface.ToGlobalName()} Create()"); // 12 spaces indent
            sb.AppendLine("            {");
            CreateCoder(sb, entry.Implementation); // 16 spaces indent
            sb.AppendLine("            }");

            sb.AppendLine($"            public {entry.Interface.ToGlobalName()} Create(IOverrides overrides)"); // 12 spaces indent
            sb.AppendLine("            {");
            CreateOverridesCoder(sb, entry.Implementation); // 16 spaces indent
            sb.AppendLine("            }");

            sb.AppendLine("        }");
        }

        // constructor
        sb.AppendLine($"        public {info.ClassName}()"); // 8 spaces indent
        sb.AppendLine("        {");
        foreach (var entry in entries) {
            if (entry.IsInstance) {
                sb.AppendLine($"            Resolvers.Add(typeof({entry.Interface.ToGlobalName()}), new SingletonResolver(s => {entry.InstanceExpression!}));");
            }
            else sb.AppendLine($"            Resolvers.Add(typeof({entry.Interface.ToGlobalName()}), new SingletonResolver<{entry.Interface.ToCreatorName()}, , {entry.Interface.ToGlobalName()}>());");
        }
        sb.AppendLine("        }");

        if (!string.IsNullOrEmpty(info.Namespace)) {
            sb.AppendLine("}");
        }

        spc.AddSource(hintName, sb.ToString());
    }

    private static void CreateCoder(StringBuilder sb, ITypeSymbol typeSymbol)
    {
        var constructors = typeSymbol.GetMembers()
            .OfType<IMethodSymbol>()
            .Where(o => o.MethodKind == MethodKind.Constructor)
            .ToArray();

        var constructorsWithArguments = constructors
            .Where(o => o.Parameters.Length > 0)
            .ToArray();

        var constructor = constructorsWithArguments.FirstOrDefault() ?? constructors.First();
        var arguments = constructor.Parameters.Select(o => o.Type).ToArray();
        var argumentsText = arguments.Select(o => $"IoC.Resolve<{o.ToGlobalName()}>()");
        sb.AppendLine($"                return new {typeSymbol.ToGlobalName()}({argumentsText.Join()})");
    }

    private static void CreateOverridesCoder(StringBuilder sb, ITypeSymbol typeSymbol)
    {
        var members = typeSymbol.GetMembers()
            .OfType<IMethodSymbol>()
            .Where(o => o.MethodKind == MethodKind.Constructor)
            .ToArray();

        var constructor = members.First();
        if (constructor == null || constructor.Parameters.Length == 0)
            // no parameters constructor
            return;

        for (int i = 0; i < constructor.Parameters.Length; i++) {
            var p = constructor.Parameters[i];
            sb.AppendLine($"                var constructor{p.Name.ToLower()}{i}Override = overrides.Constructor.Overrides.TryGetValue(\"{p.Name.ToLower()}\", out var constructor{p.Name.ToLower()}{i}Value);");
            sb.AppendLine($"                var dependency{p.Name.ToLower()}{i}Override = overrides.Dependency.Overrides.TryGetValue(typeof({p.Type.ToGlobalName()}), out var dependency{p.Name.ToLower()}{i}Func);");
        }

        sb.AppendLine("                var nextOverrides = Overrides.Create();");
        sb.AppendLine("                nextOverrides.Dependency = overrides.Dependency;");

        for (int i = 0; i < constructor.Parameters.Length; i++) {
            var p = constructor.Parameters[i];
            var parameterName = $"{p.Name.ToLower()}{i.ToString()}";
            var parameterType = p.Type.ToGlobalName();

            sb.AppendLine($"                {parameterType} {parameterName} = default;");

            sb.AppendLine($"                if(constructor{parameterName}Override)");
            sb.AppendLine("                {");
            sb.AppendLine($"                    {parameterName} = ({parameterType})constructor{parameterName}Value;");
            sb.AppendLine("                }");

            sb.AppendLine($"                if(!constructor{parameterName}Override && dependency{parameterName}Override)");
            sb.AppendLine("                {");
            sb.AppendLine($"                     {parameterName} = ({parameterType})dependency{parameterName}Func();");
            sb.AppendLine("                }");

            sb.AppendLine($"                if(!constructor{parameterName}Override && !dependency{parameterName}Override)");
            sb.AppendLine("                {");
            sb.AppendLine($"                     {parameterName} = IoC.Resolve<{parameterType}>(nextOverrides);");
            sb.AppendLine("                }");
        }

        var parameters = constructor.Parameters
            .Select((o, i) => $"{o.Name.ToLower()}{i.ToString()}")
            .Join();
        sb.AppendLine($"                return new {typeSymbol.ToGlobalName()}({parameters});");
    }

    private static void EmitError(StringBuilder sb, ContainerInfo info, IGrouping<string, ServiceEntry>[] duplicates)
    {
        // generate a comment-only partial file describing duplicates
        if (!string.IsNullOrEmpty(info.Namespace)) {
            sb.AppendLine($"namespace {info.Namespace}");
            sb.AppendLine("{");
        }

        sb.AppendLine("    // <auto-generated/>");
        sb.AppendLine($"    partial class {info.ClassName}");
        sb.AppendLine("    {");
        sb.AppendLine("        // ZeroIoC generator detected multiple registrations for the same service interface.");
        sb.AppendLine("        // Conflicting registrations (interface -> implementations):");

        foreach (var grp in duplicates) {
            sb.AppendLine($"        // Interface: {grp.Key}");
            foreach (var e in grp) {
                sb.AppendLine($"        //   -> {e.Implementation.ToDisplayString()}  (instance: {e.IsInstance})  syntax: {EscapeForComment(e.Syntax.ToString())}");
            }
            sb.AppendLine();
        }

        sb.AppendLine("    }");

        if (!string.IsNullOrEmpty(info.Namespace)) {
            sb.AppendLine("}");
        }
    }

    private static string Quote(string s)
    {
        return "@\"" + s.Replace("\"", "\"\"") + "\"";
    }

    private static string EscapeForComment(string s)
    {
        if (string.IsNullOrEmpty(s)) return string.Empty;
        return s.Replace("*/", "*\\/");
    }


}
